ECS(Fargate)をプライベートサブネットで使うときに必要なエンドポイントを、Terraformで作れるようにしてみた

ECS(Fargate)をプライベートサブネットで使うときに必要なエンドポイントを、Terraformで作れるようにしてみた

Clock Icon2024.08.02

こんにちは、ゲームソリューション部のsoraです。
今回は、ECS(Fargate)をプライベートサブネットで使うときに必要なエンドポイントを、Terraformで作れるようにしてみたことについて書いていきます。

はじめに

ECS(Fargete)をプライベートサブネットで使うとき、必須でないものもありますが、以下のエンドポイントが必要になります。

  • 必須
    • com.amazonaws.region.ecr.dkr
    • com.amazonaws.region.ecr.api
    • com.amazonaws.region.s3(Gateway)
  • 必須でない
    • com.amazonaws.region.logs
    • com.amazonaws.region.secretsmanager
    • com.amazonaws.region.ssm
    • com.amazonaws.region.ssmmessages

com.amazonaws.region.s3(Gateway)以外のエンドポイントは、放置しておくと課金が発生します。
検証で少し使いたいときも作成して削除してと面倒なため、上記エンドポイントをTerraformで簡単に作れるようにしたいというのが目的です。

前提

エンドポイントを利用するVPC・サブネット、紐づけるルートテーブルは作成済みであるとします。
(ちなみに、importブロックを消せば新規作成でもできます。)

ソースコード

以下Terraformのコードです。
検証用で利用することを想定しているため、tfstateファイルはローカルに配置します。
既存のリソースをインポートする部分は、利用する環境に合わせて入れてください。

main.tf
terraform {
    # AWSプロバイダーのバージョン指定
    required_providers {
      aws = {
        source  = "hashicorp/aws"
        version = "~> 5.60.0"
      }
    }
    backend "local" {
      path = "soratest.tfstate"
    }
  }
  provider "aws" {
    region = "ap-northeast-1"
  }

  # 既存リソースのimport(環境によって変更する箇所)
  ## VPC
  import {
    to = aws_vpc.main
    id = "vpc-xxxxxxxx"
  }
  resource "aws_vpc" "main" {
    cidr_block = "xx.xx.xx.xx/16"
    tags = {
      Name = "sora-vpc"
    }
    lifecycle {
      prevent_destroy = true
    }
  }
  ## Subnet
  import {
    to = aws_subnet.private
    id = "subnet-xxxxxxxx"
  }
  resource "aws_subnet" "private" {
    vpc_id     = aws_vpc.main.id
    cidr_block = "xx.xx.xx.xx/20"
    tags = {
      Name = "sora-private-1a"
    }
    lifecycle {
      prevent_destroy = true
    }
  }
  ## ルートテーブル
  import {
    to = aws_route_table.private
    id = "rtb-xxxxxxxx"
  }
  resource "aws_route_table" "private" {
    vpc_id = aws_vpc.main.id
    tags = {
      Name = "sora-rtb-private-1a"
    }
    lifecycle {
      prevent_destroy = true
    }
  }
  resource "aws_vpc_endpoint_route_table_association" "private_s3" {
    vpc_endpoint_id = aws_vpc_endpoint.s3.id
    route_table_id  = aws_route_table.private.id
    depends_on = [aws_vpc_endpoint.s3]
  }

  # リソースのSG
  resource "aws_security_group" "main" {
      name = "main_sg"
      vpc_id = aws_vpc.main.id
      tags = {
        Name = "test-resource-sg"
      }
  }
  resource "aws_vpc_security_group_egress_rule" "egress_main" {
    security_group_id = aws_security_group.main.id
    cidr_ipv4         = "0.0.0.0/0"
    ip_protocol       = "-1"
  }

  # エンドポイントのSG
  resource "aws_security_group" "endpoint" {
      name = "endpoint"
      vpc_id = aws_vpc.main.id
      tags = {
        Name = "endpoint-sg"
      }
  }
  resource "aws_vpc_security_group_ingress_rule" "ingress_endpoint" {
    security_group_id = aws_security_group.endpoint.id
    referenced_security_group_id = aws_security_group.main.id
    ip_protocol       = "-1"
  }
  resource "aws_vpc_security_group_egress_rule" "egress_endpoint" {
    security_group_id = aws_security_group.endpoint.id
    cidr_ipv4         = "0.0.0.0/0"
    ip_protocol       = "-1"
  }

  # エンドポイント
  ## S3(Gateway)
  resource "aws_vpc_endpoint" "s3" {
    vpc_id       = aws_vpc.main.id
    service_name = "com.amazonaws.ap-northeast-1.s3"
    tags = {
      Name = "s3-endpoint"
    }
  }
  resource "aws_vpc_endpoint_route_table_association" "s3" {
    route_table_id  = aws_route_table.private.id
    vpc_endpoint_id = aws_vpc_endpoint.s3.id
  }

  ## ECR
  resource "aws_vpc_endpoint" "ecr_api" {
    vpc_id       = aws_vpc.main.id
    service_name = "com.amazonaws.ap-northeast-1.ecr.api"
    vpc_endpoint_type = "Interface"
    private_dns_enabled = true
    subnet_ids = [aws_subnet.private.id]
    security_group_ids = [aws_security_group.endpoint.id]
    tags = {
      Name = "ecr-api-endpoint"
    }
  }
  resource "aws_vpc_endpoint" "ecr_dkr" {
    vpc_id       = aws_vpc.main.id
    service_name = "com.amazonaws.ap-northeast-1.ecr.dkr"
    vpc_endpoint_type = "Interface"
    private_dns_enabled = true
    subnet_ids = [aws_subnet.private.id]
    security_group_ids = [aws_security_group.endpoint.id]
    tags = {
      Name = "ecr-dkr-endpoint"
    }
  }

  ## CloudWatch Logs(CloudWatch Logsにログ出力する場合)
  resource "aws_vpc_endpoint" "logs" {
    vpc_id       = aws_vpc.main.id
    service_name = "com.amazonaws.ap-northeast-1.logs"
    vpc_endpoint_type = "Interface"
    private_dns_enabled = true
    subnet_ids = [aws_subnet.private.id]
    security_group_ids = [aws_security_group.endpoint.id]
    tags = {
      Name = "logs-endpoint"
    }
  }

  ## Secrets Manager(Secrets Managerの値を利用する場合)
  resource "aws_vpc_endpoint" "secretsmanager" {
    vpc_id       = aws_vpc.main.id
    service_name = "com.amazonaws.ap-northeast-1.secretsmanager"
    vpc_endpoint_type = "Interface"
    private_dns_enabled = true
    subnet_ids = [aws_subnet.private.id]
    security_group_ids = [aws_security_group.endpoint.id]
    tags = {
      Name = "secretsmanager-endpoint"
    }
  }

  ## Systems Manager(ECS ExecとSSMパラメータを利用する場合)
  resource "aws_vpc_endpoint" "ssm" {
    vpc_id       = aws_vpc.main.id
    service_name = "com.amazonaws.ap-northeast-1.ssm"
    vpc_endpoint_type = "Interface"
    private_dns_enabled = true
    subnet_ids = [aws_subnet.private.id]
    security_group_ids = [aws_security_group.endpoint.id]
    tags = {
      Name = "ssm-endpoint"
    }
  }
  resource "aws_vpc_endpoint" "ssmmessages" {
    vpc_id       = aws_vpc.main.id
    service_name = "com.amazonaws.ap-northeast-1.ssmmessages"
    vpc_endpoint_type = "Interface"
    private_dns_enabled = true
    subnet_ids = [aws_subnet.private.id]
    security_group_ids = [aws_security_group.endpoint.id]
    tags = {
      Name = "ssmmessages-endpoint"
    }
  }

使い方

リソース作成

リソースを作成するときは、普通にデプロイして問題ないです。
記述が誤っていて、importしているリソースに差分が発生していないかは要確認です。

terraform init
terraform apply

リソース削除

リソースを削除するときは、単純にdestroyしようとするとインポートしたVPC・サブネット・ルートテーブルも削除対象となってしまいます。
prevent_destroy = trueを入れているため、destroyしようとするとエラーが出るようにはなっています。)
そのため、以下を実施します。

  • importブロックのコメントアウト
  • 既存リソースを管理対象から除外
terraform state rm aws_subnet.private
terraform state rm aws_route_table.private
terraform state rm aws_vpc.main

その後に、以下コマンドを実行するとインポートしたリソースは削除対象から外れて、エンドポイントのみを削除することができます。

$ terraform destroy
Destroy complete! Resources: 14 destroyed.

参考

https://dev.classmethod.jp/articles/vpc-endpoints-for-ecs-2022/

最後に

今回は、ECS(Fargate)をプライベートサブネットで使うときに必要なエンドポイントを、Terraformで作れるようにしてみたことを記事にしました。
どなたかの参考になると幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.